﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;



namespace TriggerSystem
{
    #region content / data

    #region CRAP
    public interface ITarget
    {
        IEntity this[int index] { get; }
        List<IEntity> GetList { get; }
        void AddTarget(IEntity target);
    }

    public class Target : ITarget
    {
        List<IEntity> _entitys;

        public IEntity this[int index]
        {
            get
            {
                IEntity i = _entitys[index];
                if (i == null) { throw new Exception("No target there"); }
                else
                {
                    return i;
                }
            }
        }

        public List<IEntity> GetList { get { return _entitys; } }

        public void AddTarget(IEntity t)
        {
            _entitys.Add(t);
        }

        public Target(List<IEntity> ent) { _entitys = ent; }

        public Target(IEntity ent) { _entitys = new List<IEntity>(); _entitys.Add(ent); }
    }
    #endregion
    /// <summary>
    /// Message sent when an event occurs - SYSTEM
    /// Rethink this because has to be changed when
    /// A new event is added
    /// </summary>
    public class Hint
    {
        string message = "";
        IEntity triggerer = null;
        IEntity target = null;
        int hpHealed = 0;
        public ArtemisTest.xy pos = new ArtemisTest.xy();
        //public ArtemisTest.Entity OwnerOfComponent;

        public int HpHealed { set { hpHealed = value; } get { if (hpHealed == 0) { throw new Exception("Returned null int"); } else { return hpHealed; } } }
        public string Message { set { message = value; } get { if (message == "") { throw new Exception("Returned null string"); } else { return message; } } }
        public IEntity Triggerer { set { triggerer = value; } get { if (triggerer == null) { throw new Exception("Returned null entity"); } else { return triggerer; } } }
        public IEntity Target { set { target = value; } get { if (target == null) { throw new Exception("Returned null entity"); } else { return target; } } }
    }
    #region more crap
    public enum Color
    {
        red,
        blue,
    }

    public interface IEntity
    {

        string GetName { get; }
        int X { get; }
        int Y { get; }
    }

    public abstract class Item : TObservable, IEntity
    {
        string _name = "";
        int _x;
        int _y;

        int charges = 1;

        public virtual void Use(IEntity user, ITarget target)
        {
            charges--;
            if (charges == 0)
            {
                this.RemoveObservers();
            }
        }

        public string GetName
        {
            get { return "Item : " + _name; }
        }

        public int X { get { return _x; } set { _x = value; Notify(new Hint { Triggerer = this }, EventType.move); } }
        public int Y { get { return _y; } set { _y = value; Notify(new Hint { Triggerer = this }, EventType.move); } }
    }

    public class Potion : Item
    {
        int _hpHealed = 25;

        public override void Use(IEntity user, ITarget target)
        {
            Console.WriteLine("Potion sent");
            Notify(new Hint() { Target = target[0], Triggerer = user, HpHealed = _hpHealed }, EventType.item_used);
            base.Use(user, target);
        }

        public Potion(ActionTracker at)
        {
            Trigger t = new Trigger();
            t.AddAction(new HealAction(_hpHealed, t));
            t.Event = new TEvent(t, EventType.item_used, at);
            this.Register(t.Event);
        }
    }

    public class Inventory
    {
        IEntity _owner;
        List<Item> _items;

        public void AddItem(Item i)
        {
            _items.Add(i);
        }

        public void UseItem(int index, ITarget t)
        {
            _items[index].Use(_owner, t);
        }

        public Inventory(IEntity owner)
        {
            _owner = owner;
            _items = new List<Item>();
        }
    }

    public class Player : TObservable, IEntity
    {
        string _name;
        Color _color = Color.red;
        int _x;
        int _y;

        public string GetName { get { return _name; } }
        public Color GetColor { get { return _color; } }
        public int X { get { return _x; } set { _x = value; Notify(new Hint { Triggerer = this }, EventType.move); } }
        public int Y { get { return _y; } set { _y = value; Notify(new Hint { Triggerer = this }, EventType.move); } }

        public void SendMessage(string text, IEntity other) { Notify(new Hint { Triggerer = this, Target = other, Message = text }, EventType.msg); }
        public void SetPos(int x, int y) { X = x; Y = y; }

        public Player(string name) { _name = name; SetPos(0, 0); }
    }

    /// <summary>
    /// self , other, message
    /// </summary>
    public class OtherColorRedCondition : TCondition
    {
        public override bool IsMet(Hint h)
        {
            //if other player is red
            IEntity other = h.Target;
            return (((Player)other).GetColor == Color.red);
        }

        public OtherColorRedCondition(Trigger t) : base(t) { }
    }

    /// <summary>
    /// self , other
    /// </summary>
    public class PlayersWithinRange : TCondition
    {
        int _range;

        public override bool IsMet(Hint h)
        {
            //if other player is red
            IEntity triggerer = (IEntity)_trigger.GetObservees[0];            //(IEntity)hint[0]; //(IEntity)hint[0];
            IEntity other = (IEntity)_trigger.GetObservees[1];                //(IEntity)hint[1];//(IEntity)_other;
            bool x = Math.Abs(triggerer.X - other.X) < _range;
            bool y = Math.Abs(triggerer.Y - other.Y) < _range;
            return (x && y);
        }

        public PlayersWithinRange(Trigger t, int rangeSquared) : base(t) { _range = rangeSquared; }
    }

    /// <summary>
    /// self , other, message
    /// </summary>
    public class MessageAction : TAction
    {
        public override void Do(Hint h)
        {
            Console.WriteLine("msg recieved from " + (h.Triggerer).GetName + " for " + (h.Target).GetName + " saying " + h.Message);
        }
        public override void Undo(Hint h)
        {

        }

        public MessageAction(Trigger t) : base(t) { _trigger = t; }
    }

    /// <summary>
    /// No args
    /// </summary>
    public class HealAction : TAction
    {
        int _amount;

        public override void Do(Hint h)
        {
            Console.WriteLine(h.Triggerer.GetName + " healed " + h.Target.GetName);
        }

        public override void Undo(Hint h)
        {
            Console.WriteLine(h.Triggerer.GetName + " unhealed" + h.Target.GetName);
        }

        public HealAction(int amount, Trigger t)
            : base(t)
        {
            _amount = amount;
            _usage.IsPeriodic = true;
            _usage.MaxExecutions = 3;
            _usage.PeriodicDelay = 1;
            _usage.Delay = 5;
        }
    }

    #endregion
    //public class UpdatePositionInQTForEntity : TAction
    //{
    //    ArtemisTest.EntitySystem _es;

    //    public UpdatePositionInQTForEntity(Trigger t, ArtemisTest.EntitySystem es)
    //        : base(t)
    //    {
    //        _es = es;
    //    }

    //    public override void Do(Hint h)
    //    {
    //        //call to physics first to check if position available?
    //        Console.WriteLine("Updated Position");
    //        _es.OnPositionUpdate(h.OwnerOfComponent);// _qt.UpdatePosition(h.pos, h.OwnerOfComponent.UID);
    //    }

    //    public override void Undo(Hint h)
    //    {
    //        throw new System.NotImplementedException();
    //    }
    //}

    /// <summary>
    /// self , other
    /// </summary>
    public class PushUnitAwayFromUnit : TAction
    {
        int _amount;
        int _x;
        int _y;
        Player _player;
        public override void Do(Hint h)
        {
            _player = ((Player)(_trigger.GetObservees[1]));
            _x = _player.X;
            _y = _player.Y;
            _player.SetPos(256, 256);
            Console.WriteLine(_player.GetName + " was pushed");
        }

        public override void Undo(Hint h)
        {
            _player.X = _x;
            _player.Y = _y;
            Console.WriteLine(_player.GetName + " was unpushed");
            //_player = null;
        }

        public PushUnitAwayFromUnit(Trigger t, int amount) : base(t) { _amount = amount; }
    }

    #endregion

    #region Trigger System

    /// <summary>
    /// Watches for its observed entitys to notify()
    /// which signals than an event has occured
    /// and then the trigger is fired.
    /// </summary>
    public class TEvent
    {
        ActionTracker _tracker;
        Trigger _trigger;                   //parent trigger
        EventType _type;                    //type of event
        List<IObservable> _observables;     //entitys that can perform the event.
        bool _hasActionTracker; 

        public List<IObservable> Observees { get { return _observables; } }
        public EventType Type { get { return _type; } }

        public virtual void AddObservee(object entity) { _observables.Add((IObservable)entity); }
        public virtual void RemoveObservee(object entity) { _observables.Remove((IObservable)entity); }

        public virtual void Fire(Hint h)
        {
            if (_hasActionTracker)
            {
                for (int i = 0; i < _trigger.Actions.Count; i++)
                {
                    _tracker.AddAction(_trigger.Actions[i], h);
                }
            }
            else
            {
                _trigger.Fire(h);
            }
        }

        public TEvent(Trigger t, EventType type, ActionTracker tracker = null)
        {
            if (tracker == null) { _hasActionTracker = false; } else { _hasActionTracker = true; }
            _trigger = t; _type = type; _tracker = tracker;
            _observables = new List<IObservable>();
        }
    }

    /// <summary>
    /// Rework this.
    /// Has to be modified everytime an event is added.
    /// </summary>
    public enum EventType
    {
        msg,
        move,
        item_used,
        entity_moved,
    }

    public interface IObservable
    {
        void Register(TEvent obs);
        void RemoveObservers();
        void Notify(Hint h, EventType e);
    }

    /// <summary>
    /// Anything that can be watched by a trigger
    /// must implement this
    /// </summary>
    public abstract class TObservable : IObservable
    {
        List<TEvent> _observers;

        //adds its observer to its own list, adds itself to the observers list of observees
        public void Register(TEvent obs) { _observers.Add(obs); obs.AddObservee(this); }
        public void RemoveObservers()
        {
            for (int i = _observers.Count - 1; i >= 0; i--)
            {
                _observers[i].RemoveObservee(this); _observers.Remove(_observers[i]);
            }
        }

        /// <summary>Signals that an event has occured/// </summary>
        public void Notify(Hint h, EventType e) { foreach (TEvent obs in _observers) { if (e == obs.Type) obs.Fire(h); } }

        public TObservable() { _observers = new List<TEvent>(); }
    }

    //Strategy Pattern
    //Can access trigger internals
    public interface ICondition
    {
        Trigger GetTrigger { get; }
        bool IsMet(Hint h);
    }

    /// <summary>All trigger conditions must implement this
    ///Data is accessed through GetTrigger or Hint </summary>
    public abstract class TCondition : ICondition
    {
        protected Trigger _trigger;

        public Trigger GetTrigger
        {
            get { return _trigger; }
        }

        public abstract bool IsMet(Hint h);


        public TCondition(Trigger t) { _trigger = t; }
    }

    //Strategy
    public interface IAction
    {
        Trigger GetTrigger { get; }
        void Do(Hint h);
        void Undo(Hint h);
    }

    public class ActionTracker
    {
        long _tick = 0;
        Queue<TAction> _actions;
        Queue<Hint> _hints;
        Stack<TAction> _executedActions;
        Stack<Hint> _executedHints;

        public long Tick { get { return _tick; } }
        public ActionTracker()
        {
            _actions = new Queue<TAction>();
            _hints = new Queue<Hint>();
            _executedActions = new Stack<TAction>();
            _executedHints = new Stack<Hint>();
        }

        public void AddAction(TAction a, Hint h)
        {
            _actions.Enqueue(a);
            _hints.Enqueue(h);
        }

        public void RunActions()
        {
            _tick++;
            Queue<Hint> nh = new Queue<Hint>();
            Queue<TAction> na = new Queue<TAction>();

            while (_actions.Count > 0)
            {
                //take action + hint from queue
                TAction n = _actions.Dequeue();
                Hint h = _hints.Dequeue();

                //set start time to current time (if default)
                if (n._usage.StartTime == 0) { n._usage.StartTime = _tick; }

                //if action older than cooldown time
                if (n._usage.CanFire(_tick))
                {
                    //Fire the trigger and return if it worked
                    bool executed = n.GetTrigger.Fire(h);

                    if (n._usage.Removeable())
                    {
                        //action done so add to history
                        _executedActions.Push(n);
                        _executedHints.Push(h);
                    }
                    else
                    {
                        //action is going to be repeated,
                        //add back to action list
                        nh.Enqueue(h);
                        na.Enqueue(n);
                    }
                }
                else
                {
                    //action didnt work, add back to action list
                    nh.Enqueue(h);
                    na.Enqueue(n);
                }
            }

            //update lists
            _actions = na;
            _hints = nh;
        }
    }

    //Data class for managaging time responsibilities of an action 
    public class ActionUsage
    {
        long _startTime;
        bool _isPeriodic;
        int _delay;
        int _periodicDelay;
        int _executions;
        int _maxExecutions;

        public int PeriodicDelay { get { return _periodicDelay; } set { _periodicDelay = value; } }
        public bool IsPeriodic { set { _isPeriodic = value; } get { return _isPeriodic; } }
        public int Delay { set { _delay = value; } get { return _delay; } }
        public int MaxExecutions { set { _maxExecutions = value; } get { return _maxExecutions; } }
        public long StartTime { set { _startTime = value; } get { return _startTime; } }

        //is the action not going to be repeated, or has been fully used
        public bool Removeable()
        {
            if (_isPeriodic == false) { return true; }
            else
            {
                if (_executions >= _maxExecutions)
                {
                    return true;
                }
            }
            return false;
        }

        //is the cooldown ready
        public bool CanFire(long currentTime)
        {
            int delay;

            if (_executions > 0) { delay = _periodicDelay; } else { delay = _delay; }

            if (currentTime >= _startTime + delay)
            {
                if (_isPeriodic)
                {
                    if (_executions <= _maxExecutions)
                    {
                        _executions++;
                        _startTime = currentTime;
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }


        public ActionUsage()
        {
            _startTime = 0;
            _isPeriodic = false;
            _periodicDelay = 0;
            _delay = 0;
            _executions = 0;
            _maxExecutions = 1;
        }
    }

    /// <summary>All trigger actions must implement this
    ///Data is accessed through GetTrigger or Hint </summary>
    public abstract class TAction : IAction
    {
        protected Trigger _trigger;
        public ActionUsage _usage;//

        public Trigger GetTrigger
        {
            get { return _trigger; }
        }

        public TAction(Trigger t) { _trigger = t; _usage = new ActionUsage(); }//

        public abstract void Do(Hint h);
        public abstract void Undo(Hint h);
    }

    /// <summary>
    /// Example Usage
    /// Trigger t = new Trigger();
    /// t.Action = new HealAction(_hpHealed, t);
    /// t.Event = new TEvent(t, EventType.item_used);
    /// this.Register(t.Event); where this is a TObserable
    /// </summary>
    public class Trigger
    {
        TEvent _event;
        List<TCondition> _conditions = new List<TCondition>();
        List<TAction> _actions = new List<TAction>();

        public List<IObservable> GetObservees { get { return Event.Observees; } }
        public TEvent Event { set { _event = value; } get { return _event; } }
        public List<TAction> Actions { get { return _actions; } }
        //public TCondition Condition { set { _condition = value; } get { return _condition; } }
        public void AddCondition(TCondition cond) { _conditions.Add(cond); }
        public void AddAction(TAction action) { _actions.Add(action); }
        //public IAction Action { set { _action = value; } get { return _action; } }

        /// <summary>if all conditions are true do all actions/// </summary>
        public bool Fire(Hint h)
        {
            //if all conditions are true do all actions
            if (_conditions.Count == 0) { foreach (TAction a in _actions) { a.Do(h); } }
            else
            {
                bool check = true;
                foreach (TCondition c in _conditions)
                {
                    if (c.IsMet(h) == false) { check = false; }
                }
                if (check)
                {
                    foreach (TAction a in _actions)
                    {
                        a.Do(h);
                    }
                    return true;    //return fired successfully
                }
            }
            return false;   //return didnt fire
        }
    }
    #endregion

    //class Program
    //{
    //    static void Main(string[] args)
    //    {
    //        ActionTracker tracker = new ActionTracker();

    //        Player tom = new Player("Tom");
    //        Player andy = new Player("Andy");

    //        Trigger t = new Trigger();
    //        Trigger t2 = new Trigger();

    //        TEvent me = new TEvent(t, EventType.msg, tracker);
    //        TEvent me2 = new TEvent(t2, EventType.move, tracker);
    //        tom.Register(me);
    //        tom.Register(me2);
    //        andy.Register(me);
    //        andy.Register(me2);

    //        t.AddAction(new MessageAction(t));
    //        t.AddCondition(new OtherColorRedCondition(t));
    //        t.Event = me;

    //        t2.AddAction(new PushUnitAwayFromUnit(t2, 5));
    //        t2.AddCondition(new PlayersWithinRange(t2, 25));
    //        t2.Event = me2;

    //        tom.SendMessage("hey andy", andy);
    //        tom.SendMessage("hey andy2", andy);

    //        tom.SetPos(0, 0);

    //        Inventory tomsInventory = new Inventory(tom);
    //        tomsInventory.AddItem(new Potion(tracker));
    //        tomsInventory.AddItem(new Potion(tracker));
    //        tomsInventory.UseItem(0, new Target(andy));



    //        while (true)
    //        {


    //            tracker.RunActions();
    //        }
    //    }
    //}
}

